﻿using System;
using dnAnalytics.LinearAlgebra;
using dnAnalytics.LinearAlgebra.Decomposition;
using dnAnalytics.LinearAlgebra.IO;
using NUnit.Framework;

namespace dnAnalytics.Tests.LinearAlgebra.Decomposition
{
    [TestFixture]
    public class GramSchmidtTests
    {
        private readonly MatrixMarketReader matrixReader = new MatrixMarketReader();

        [Test]
        [ExpectedException(typeof (ArgumentNullException))]
        public void ConstructorNull()
        {
            new GramSchmidt(null);
        }


        [Test]
        [ExpectedException(typeof (InvalidMatrixOperationException))]
        public void WideMatrixThrowsInvalidMatrixOperationException()
        {
            new GramSchmidt(new DenseMatrix(3,4));
        }

        [Test]
        public void NotFullRank()
        {
            Matrix singularMatrix = matrixReader.ReadMatrix("./TestData/Matrices/gear_integer_general_coordinate_100.mtx", StorageType.Dense);
            GramSchmidt gs = new GramSchmidt(singularMatrix);
            Assert.IsFalse(gs.IsFullRank());
        }

        [Test]
        public void FullRank()
        {
            Matrix matrix = matrixReader.ReadMatrix("./TestData/Matrices/random_real_general_array_100.mtx", StorageType.Dense);
            GramSchmidt gs = new GramSchmidt(matrix);
            Assert.IsTrue(gs.IsFullRank());
        }

        [Test]
        public void SquareFactors()
        {
            Matrix squareMatrix = matrixReader.ReadMatrix("./TestData/Matrices/random_real_general_array_100.mtx", StorageType.Dense);
            GramSchmidt gs = new GramSchmidt(squareMatrix);

            Matrix q = gs.Q();
            Matrix r = gs.R();

            Matrix result = new DenseMatrix(squareMatrix.Rows, squareMatrix.Columns);
            q.Multiply(r, result);
            for (int i = 0; i < result.Rows; i++)
            {
                for (int j = 0; j < result.Columns; j++)
                {
                    double error;
                    if (System.Math.Abs(squareMatrix[i, j]) < 1e10)
                    {
                        error = System.Math.Abs(result[i, j] - squareMatrix[i, j]);
                    }
                    else
                    {
                        error = System.Math.Abs((result[i, j] - squareMatrix[i, j])/squareMatrix[i, j]);
                    }
                    Assert.IsTrue(error < Constants.AcceptableError);
                }
            }
        }

        [Test]
        public void TallFactors(){
            Matrix tallMatrix = matrixReader.ReadMatrix("./TestData/Matrices/random_real_general_array_20_10.mtx", StorageType.Dense);
            GramSchmidt gs = new GramSchmidt(tallMatrix);

            Matrix q = gs.Q();
            Matrix r = gs.R();

            Matrix result = new DenseMatrix(tallMatrix.Rows, tallMatrix.Columns);
            q.Multiply(r, result);

            for (int i = 0; i < result.Rows; i++)
            {
                for (int j = 0; j < result.Columns; j++)
                {
                    double error;
                    if (System.Math.Abs(tallMatrix[i, j]) < 1e10)
                    {
                        error = System.Math.Abs(result[i, j] - tallMatrix[i, j]);
                    }
                    else
                    {
                        error = System.Math.Abs((result[i, j] - tallMatrix[i, j]) / tallMatrix[i, j]);
                    }
                    Assert.IsTrue(error < Constants.AcceptableError);
                }
            }
        }

        [Test]
        public void Determinant()
        {
            Matrix squareMatrix = matrixReader.ReadMatrix("./TestData/Matrices/random_real_general_array_100.mtx", StorageType.Dense);
            GramSchmidt gs = new GramSchmidt(squareMatrix);
            
            double expected = 5.006656015927541e248;
            double actual = gs.Determinant();
            double error = System.Math.Abs((actual - expected) / expected);
            Assert.IsTrue(error < Constants.AcceptableError);
        }

        [Test]
        public void SingularDeterminant()
        {
            Matrix singularMatrix = matrixReader.ReadMatrix("./TestData/Matrices/gear_integer_general_coordinate_100.mtx", StorageType.Dense);
            GramSchmidt gs = new GramSchmidt(singularMatrix);
            Assert.AreEqual(0, gs.Determinant(), Constants.AcceptableError);
        }

    }
}